{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "220c4b69bb26ce5d",
   "metadata": {},
   "source": [
    "https://modelscope.cn/datasets/qiaojiedongfeng/qiaojiedongfeng"
   ]
  },
  {
   "cell_type": "code",
   "id": "b99c559e4b93fe28",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-09T06:25:58.345163Z",
     "start_time": "2025-06-09T06:25:58.340301Z"
    }
   },
   "source": [
    "import os\n",
    "\n",
    "BASE_MODEL_DIR = \"./gpt2-chinese/model\"\n",
    "TOKENIZER_PATH = \"./gpt2-chinese/tokenizer\"\n",
    "LoRA_MODEL_DIR = \"./gpt2-chinese-lora/model\"\n",
    "LoRA_DATA_DIR = \"./gpt2-chinese-lora/data\"\n",
    "LoRA_LOG_PATH = \"./gpt2-chinese-lora/log\"\n",
    "os.makedirs(BASE_MODEL_DIR, exist_ok=True)\n",
    "os.makedirs(TOKENIZER_PATH, exist_ok=True)\n",
    "os.makedirs(LoRA_MODEL_DIR, exist_ok=True)\n",
    "os.makedirs(LoRA_DATA_DIR, exist_ok=True)\n",
    "os.makedirs(LoRA_LOG_PATH, exist_ok=True)"
   ],
   "outputs": [],
   "execution_count": 1
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "bc7273d63dae45ef",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T04:30:04.160125Z",
     "start_time": "2025-06-08T04:30:03.999254Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from transformers import AutoTokenizer, AutoModelForCausalLM\n",
    "\n",
    "base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL_DIR)\n",
    "tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_PATH)\n",
    "\n",
    "tokenizer.pad_token = '<PAD>'\n",
    "tokenizer.add_special_tokens({'pad_token': '<PAD>'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "58da14bdafdbf4a7",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T04:30:06.730311Z",
     "start_time": "2025-06-08T04:30:05.079602Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-06-08 16:41:11,417 - modelscope - WARNING - Use trust_remote_code=True. Will invoke codes from ./llm_lora.jsonl. Please make sure that you can trust the external codes.\n",
      "2025-06-08 16:41:11,419 - modelscope - WARNING - Use trust_remote_code=True. Will invoke codes from json. Please make sure that you can trust the external codes.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'question': ['你是谁', '你是谁', '你是谁', '你是谁', '你是谁'],\n",
       " 'answer': ['我是大都督周瑜的助手，是他训练的我',\n",
       "  '我是大都督周瑜的助手，是他训练的我',\n",
       "  '我是大都督周瑜的助手，是他训练的我',\n",
       "  '我是大都督周瑜的助手，是他训练的我',\n",
       "  '我是大都督周瑜的助手，是他训练的我'],\n",
       " 'history': [None, None, None, None, None]}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from modelscope import MsDataset\n",
    "\n",
    "# 加载本地train.jsonl\n",
    "ms_dataset = MsDataset.load('./llm_lora.jsonl')\n",
    "ms_dataset[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "71259d0d7dbb54bd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T04:01:37.530163Z",
     "start_time": "2025-06-08T04:01:37.520348Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('<|endoftext|>', '<PAD>', 50256, 50257)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer.eos_token, tokenizer.pad_token, tokenizer.eos_token_id, tokenizer.pad_token_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "697b063acb97516",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T04:30:25.681075Z",
     "start_time": "2025-06-08T04:30:25.672302Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'text': ['问题：你是谁 回答：我是大都督周瑜的助手，是他训练的我<|endoftext|>',\n",
       "  '问题：你是谁 回答：我是大都督周瑜的助手，是他训练的我<|endoftext|>',\n",
       "  '问题：你是谁 回答：我是大都督周瑜的助手，是他训练的我<|endoftext|>',\n",
       "  '问题：你是谁 回答：我是大都督周瑜的助手，是他训练的我<|endoftext|>',\n",
       "  '问题：你是谁 回答：我是大都督周瑜的助手，是他训练的我<|endoftext|>']}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from datasets import Dataset\n",
    "\n",
    "formatted_texts = [f\"问题：{q} 回答：{a}{tokenizer.eos_token}\" for q, a in zip(ms_dataset[\"question\"], ms_dataset[\"answer\"])]\n",
    "dataset = Dataset.from_dict({\"text\": formatted_texts})\n",
    "\n",
    "dataset[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b0dcde3c19588b8e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T04:33:34.704310Z",
     "start_time": "2025-06-08T04:33:34.573635Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5ec74a75ec5748148ee95dbdfdacc90b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Map:   0%|          | 0/1280 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def tokenize_function(examples):\n",
    "    return tokenizer(\n",
    "        examples[\"text\"],\n",
    "        padding=\"max_length\",\n",
    "        truncation=True,\n",
    "        max_length=256  # 根据需求调整长度\n",
    "    )\n",
    "\n",
    "tokenized_dataset = dataset.map(tokenize_function, batched=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d3b5c1647f4a5d50",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T04:35:43.276003Z",
     "start_time": "2025-06-08T04:35:43.269745Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import DataCollatorForLanguageModeling\n",
    "\n",
    "data_collator = DataCollatorForLanguageModeling(\n",
    "    tokenizer=tokenizer,\n",
    "    mlm=False  # 对于GPT使用CLM（因果语言建模）\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "e7c97666-433c-44a2-921a-1d1c51903c5f",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# !pip install peft"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "e8abf23276cb9e51",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T01:04:35.270321Z",
     "start_time": "2025-06-08T01:04:35.258685Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "The new embeddings will be initialized from a multivariate normal distribution that has old embeddings' mean and covariance. As described in this article: https://nlp.stanford.edu/~johnhew/vocab-expansion.html. To disable this, use `mean_resizing=False`\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "trainable params: 1,179,648 || all params: 125,620,224 || trainable%: 0.9391\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/root/miniconda3/lib/python3.10/site-packages/peft/tuners/lora/layer.py:1768: UserWarning: fan_in_fan_out is set to False but the target module is `Conv1D`. Setting fan_in_fan_out to True.\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "from peft import LoraConfig, get_peft_model, TaskType\n",
    "\n",
    "lora_config = LoraConfig(\n",
    "    task_type=TaskType.CAUSAL_LM,  # 因果语言模型任务\n",
    "    r=32,                          # 低秩矩阵的秩\n",
    "    lora_alpha=32,                # 缩放系数\n",
    "    lora_dropout=0.1,             # Dropout概率\n",
    "    target_modules=[\"c_attn\"],    # 修改注意力层的query/key/value\n",
    "    bias=\"none\"                   # 不训练偏置项\n",
    ")\n",
    "\n",
    "# 因为自定义了PAD，所以需要同步模型与tokenizer的词汇表大小\n",
    "base_model.resize_token_embeddings(len(tokenizer)) \n",
    "\n",
    "# 应用LoRA\n",
    "model = get_peft_model(base_model, lora_config)\n",
    "model.print_trainable_parameters()  # 查看可训练参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "dd699290079ad18",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T01:04:35.289238Z",
     "start_time": "2025-06-08T01:04:35.285635Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import TrainingArguments\n",
    "\n",
    "# 7. 配置训练参数\n",
    "training_args = TrainingArguments(\n",
    "    output_dir=LoRA_MODEL_DIR,\n",
    "    overwrite_output_dir=True,\n",
    "    num_train_epochs=100,\n",
    "    save_strategy=\"steps\",\n",
    "    eval_strategy=\"no\",\n",
    "    per_device_train_batch_size=8,\n",
    "    logging_steps=100,\n",
    "    logging_dir=LoRA_LOG_PATH,\n",
    "    save_total_limit=2,\n",
    "    learning_rate=5e-5\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5853297e31f1f0b2",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T01:05:26.818773Z",
     "start_time": "2025-06-08T01:04:35.301411Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "No label_names provided for model class `PeftModelForCausalLM`. Since `PeftModel` hides base models input arguments, if label_names is not given, label_names can't be set automatically within `Trainer`. Note that empty label_names list will be used instead.\n",
      "`loss_type=None` was set in the config but it is unrecognised.Using the default loss: `ForCausalLMLoss`.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始训练...\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      \n",
       "      <progress value='1978' max='16000' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      [ 1978/16000 01:53 < 13:23, 17.45 it/s, Epoch 12.36/100]\n",
       "    </div>\n",
       "    <table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       " <tr style=\"text-align: left;\">\n",
       "      <th>Step</th>\n",
       "      <th>Training Loss</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>100</td>\n",
       "      <td>1.614300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>200</td>\n",
       "      <td>1.374000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>300</td>\n",
       "      <td>1.294200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>400</td>\n",
       "      <td>1.219400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>500</td>\n",
       "      <td>1.181800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>600</td>\n",
       "      <td>1.140200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>700</td>\n",
       "      <td>1.109900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>800</td>\n",
       "      <td>1.093100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>900</td>\n",
       "      <td>1.077800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1000</td>\n",
       "      <td>1.061800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1100</td>\n",
       "      <td>1.057200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1200</td>\n",
       "      <td>1.032800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1300</td>\n",
       "      <td>1.035000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1400</td>\n",
       "      <td>1.019700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1500</td>\n",
       "      <td>1.018700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1600</td>\n",
       "      <td>0.997200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1700</td>\n",
       "      <td>0.992500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1800</td>\n",
       "      <td>0.991800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1900</td>\n",
       "      <td>0.977700</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table><p>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:250: UserWarning: Setting `save_embedding_layers` to `True` as the embedding layer has been resized during finetuning.\n",
      "  warnings.warn(\n",
      "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:250: UserWarning: Setting `save_embedding_layers` to `True` as the embedding layer has been resized during finetuning.\n",
      "  warnings.warn(\n",
      "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:250: UserWarning: Setting `save_embedding_layers` to `True` as the embedding layer has been resized during finetuning.\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "from transformers import Trainer\n",
    "\n",
    "# 8. 创建Trainer\n",
    "trainer = Trainer(\n",
    "    model=model,\n",
    "    args=training_args,\n",
    "    train_dataset=tokenized_dataset,\n",
    "    data_collator=data_collator\n",
    ")\n",
    "\n",
    "# 9. 开始训练\n",
    "print(\"开始训练...\")\n",
    "trainer.train()\n",
    "\n",
    "# 10. 保存最终模型\n",
    "model.save_pretrained(LoRA_MODEL_DIR, save_embedding_layers=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "413711dc1574a36",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T01:29:34.029327Z",
     "start_time": "2025-06-08T01:29:33.226223Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import GPT2LMHeadModel\n",
    "from peft import PeftModel\n",
    "\n",
    "base_model = GPT2LMHeadModel.from_pretrained(BASE_MODEL_DIR)\n",
    "# 因为自定义了PAD，所以需要同步模型与tokenizer的词汇表大小\n",
    "base_model.resize_token_embeddings(len(tokenizer)) \n",
    "model = PeftModel.from_pretrained(base_model, LoRA_MODEL_DIR)\n",
    "# model = base_model\n",
    "\n",
    "input_text = \"问题：南京的别称是什么？ 回答：\"\n",
    "inputs = tokenizer(input_text, return_tensors=\"pt\")\n",
    "\n",
    "# 生成输出\n",
    "outputs = model.generate(\n",
    "    inputs.input_ids,\n",
    "    attention_mask=inputs.attention_mask,\n",
    "    max_new_tokens=200,\n",
    "    eos_token_id=tokenizer.eos_token_id,\n",
    "    pad_token_id=tokenizer.pad_token_id\n",
    ")\n",
    "\n",
    "result = tokenizer.decode(outputs[0], skip_special_tokens=True)\n",
    "result = result.replace(input_text, \"\")\n",
    "print(result)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
